//***************************************************************************
//   Copyright(c)2020, Xidian University D405.
//           All rights reserved
//
//   File name       :   axi_master_read_channel_fsm.v
//   Module name     :   axi_master_read_channel_fsm
//   Author          :   Zhao Yuchen
//   Date            :   2022/04/06
//   Version         :   v0.95
//   Edited by       :   Zhao Yuchen
//---------------------------------------------------------------------------
// Changelog : See axi_master_interface_wrapper.v
// Interface list :
//                AXI Master
//                FIFO Control
//***************************************************************************
//`define AXI_UNALIGN_EN
//`undef VCS_MODEL
module axi_master_read_channel_fsm #
(
    //parameter integer AXI_UNALIGN_ADDR_EN   = 1,
    parameter integer AXI_BURST_LEN         = 256,
	parameter integer AXI_ID_WIDTH          = 4,
	parameter integer AXI_ADDR_WIDTH        = 32,
	parameter integer AXI_DATA_WIDTH        = 128,
    parameter integer AXI_LIB_WIDTH         = 13, //Width of Max Transmit Length(in BYTES)
	parameter integer AXI_AWUSER_WIDTH      = 0,
	parameter integer AXI_ARUSER_WIDTH      = 0,
	parameter integer AXI_WUSER_WIDTH       = 0,
	parameter integer AXI_RUSER_WIDTH       = 0,
	parameter integer AXI_BUSER_WIDTH       = 0,
	parameter integer AXI_MAX_INSTR_NUM     = 32 //Max transaction(outstanding) number
)
(
    input  wire                           M_AXI_ACLK_I,      //AXI clock input
	input  wire                           M_AXI_ARESETN_I,   //AXI master rstn input
    output wire [AXI_ID_WIDTH-1 : 0]      M_AXI_ARID_O,
	output wire [AXI_ADDR_WIDTH-1 : 0]    M_AXI_ARADDR_O,
	output wire [7 : 0]                   M_AXI_ARLEN_O,
	output wire [2 : 0]                   M_AXI_ARSIZE_O,
	output wire [1 : 0]                   M_AXI_ARBURST_O,
	output wire                           M_AXI_ARLOCK_O,
	output wire [3 : 0]                   M_AXI_ARCACHE_O,
	output wire [2 : 0]                   M_AXI_ARPROT_O,
	output wire [3 : 0]                   M_AXI_ARQOS_O,
	output wire [AXI_ARUSER_WIDTH-1 : 0]  M_AXI_ARUSER_O,
	output wire                           M_AXI_ARVALID_O,
	input  wire                           M_AXI_ARREADY_I,
    //AXI Reading Data signals
	input  wire [AXI_ID_WIDTH-1 : 0]      M_AXI_RID_I,
	input  wire [AXI_DATA_WIDTH-1 : 0]    M_AXI_RDATA_I,
	input  wire [1 : 0]                   M_AXI_RRESP_I,
	input  wire                           M_AXI_RLAST_I,
	input  wire [AXI_RUSER_WIDTH-1 : 0]   M_AXI_RUSER_I,
	input  wire                           M_AXI_RVALID_I,
	output wire                           M_AXI_RREADY_O,
	
	//******  [External Ports] Read Channel  ******//
	input  wire [AXI_ID_WIDTH-1 : 0]      read_id_i,                 //read trasaction id input
	input  wire [AXI_ADDR_WIDTH-1 : 0]    read_addr_i,               //read transaction address input                                              
	input  wire [AXI_LIB_WIDTH-1 : 0]     read_len_in_byte_i,        //read transaction length(in byte) input      
	input  wire                           read_desc_en_i,            //write descriptor enable
	input  wire                           read_desc_type_i, 
    input  wire                           read_data_10g40g_sel_i,   //0:10g    1:40                                   
	output reg  [AXI_DATA_WIDTH-1 : 0]    read_data_o,               //read data output(to an external RxFIFO)                                    
	output wire                           read_ready_o,              //receive read command ready                                                  
	input  wire                           start_read_i,              //start a read transaction(input a write command) use posedge or a pulse input
  
	output wire                           read_fifo_wr_en_o,         //control the external RxFIFO, save data received(read) from AXI slave
	input  wire                           read_fifo_wr_full_i,       //RxFIFO full signal. (control RREADY low)
                                                                     //if RxFIFO is full when AXI master receiving(reading), it usually cause pause.
    output wire [1 : 0]                   read_data_or_desc_sel_o,   //01:data  10:descriptor 00&11:undefine
    output wire [1 : 0]                   read_desc_fifo_sel_o,
    output wire [1 : 0]                   read_data_txfifo_sel_o,
    
    output wire                           read_data_done_o,
    output wire                           read_desc_done_o,
    output wire                           read_data_almost_done_o,
    output wire                           read_desc_almost_done_o,
    output wire [5 : 0]                   read_fsm_state_o
`ifndef VCS_MODEL
    ,input  wire [9 : 0]                  ram_2p_cfg_register,
    input  wire [6 : 0]                   rf_2p_cfg_register
`endif
);
    function integer clogb2 (input integer bit_depth);              
    begin: bit_width_calc
        integer bit_depth_tmp;    
        bit_depth_tmp = bit_depth;                                         
        for(clogb2=0; bit_depth_tmp>0; clogb2=clogb2+1)                   
            bit_depth_tmp = bit_depth_tmp >> 1;                                 
    end                                                           
    endfunction    
    
    //localparam integer UNALIGN_ADDR_EN     = 1;
    localparam integer BST_CNT_WIDTH       = clogb2(AXI_BURST_LEN);                             //burst counter width    
    localparam integer TRN_CNT_WITDH       = clogb2(AXI_MAX_INSTR_NUM);                         //transaction counter width
    localparam integer DOCW                = clogb2(AXI_DATA_WIDTH/8);                            //DATA_OFFSET_CNT_WIDTH
    localparam integer INSTR_WIDTH         = AXI_ID_WIDTH + AXI_ADDR_WIDTH + AXI_LIB_WIDTH;     //instruction(a.k.a. write command) width, includes id\addr\len etc.
    localparam integer ADDR_ALIGN_BOUNDARY = AXI_DATA_WIDTH / 'd8;                              //addr and data align boundary
    localparam integer ADDR2LEN_BIT        = $unsigned(clogb2(ADDR_ALIGN_BOUNDARY)) - 'd1;                 //used to calculate burst length
    localparam integer DATAFSM_FIFO_DW     = INSTR_WIDTH+AXI_LIB_WIDTH+5;         //data fsm fifo data width
    localparam integer FIFO_LINE_LEN_BYTE  = 'd16;
    
    //parameter of read channel FSM state
    localparam  R_IDLE        = 6'b000001;
    localparam  R_FETCH_INSTR = 6'b000010;
    localparam  R_WADDR       = 6'b000100;
    localparam  R_WDATA       = 6'b001000;
    localparam  R_CHKID       = 6'b010000;
    localparam  R_PAUSE       = 6'b100000;
    
    reg                             axi_arvalid;
    reg  [AXI_ADDR_WIDTH-1 : 0]     axi_araddr;
    reg                             axi_rready;
    reg  [BST_CNT_WIDTH-1 : 0]      read_index;
    reg  [BST_CNT_WIDTH-1 : 0]      read_index_total;
    reg  [BST_CNT_WIDTH-1 : 0]      read_index_total_ff;
    reg  [5:0]                      r_addr_state;
    reg  [5:0]                      r_addr_next_state;
    reg  [5:0]                      r_data_state;
    reg  [5:0]                      r_data_next_state;
    reg  [5:0]                      r_data_state_r;
    wire                            bchk_ready;
    wire [AXI_ID_WIDTH-1 : 0]       bchk_id;
    wire [AXI_ADDR_WIDTH-1 : 0]     bchk_addr;
    wire [AXI_ADDR_WIDTH-1 : 0]     bchk_addr_align;
    wire [AXI_LIB_WIDTH-1 : 0]      bchk_bytes;
    wire [AXI_LIB_WIDTH-1 : 0]      bchk_bytes_total;
    wire                            bchk_rdesc_en;   
    wire                            bchk_rdesc_type;
    wire                            bchk_fifo_sel;
    wire                            bchk_begin_of_a_task; 
    wire                            bchk_end_of_a_task;
    wire                            instr_fifo_full;
    wire                            instr_fifo_empty;
    wire                            instr_fifo_wr_en;
    reg                             instr_fifo_rd_en;
    wire [AXI_ID_WIDTH-1 : 0]       instr_id;
    wire [AXI_ADDR_WIDTH-1 : 0]     instr_addr;
    wire [AXI_ADDR_WIDTH-1 : 0]     instr_addr_end;
    wire [AXI_LIB_WIDTH-1 : 0]      instr_bytes;
    wire [AXI_ID_WIDTH-1 : 0]       data_fsm_id;
    wire [AXI_ADDR_WIDTH-1 : 0]     data_fsm_addr;
    wire [AXI_ADDR_WIDTH-1 : 0]     data_fsm_addr_end_part;
    wire [AXI_ADDR_WIDTH-1 : 0]     data_fsm_addr_end_total;
    wire [AXI_LIB_WIDTH-1 : 0]      data_fsm_bytes;
    wire [AXI_LIB_WIDTH-1 : 0]      data_fsm_bytes_total;
    wire                            data_fsm_rdesc_en;
    wire                            data_fsm_rdesc_type;
    wire                            data_fsm_fifo_sel;
    wire                            data_fsm_begin_of_a_task;
    wire                            data_fsm_end_of_a_task;
    wire [TRN_CNT_WITDH:0]          read_transaction_cnt;
    //reg  [TRN_CNT_WITDH:0]          read_transaction_cnt_ff;
    wire                            write_addr_done;
    reg                             read_data_done;
    wire                            start_read_data_pulse;
    reg                             start_read_data_pulse_ff;
    reg                             start_read_data_pulse_r;
    reg                             read_active;
    reg                             rdone_flag;
    wire                            rnext;
    reg  [7 : 0]                    read_len_for_addr_ch;
    wire [7 : 0]                    read_len_for_data_ch;
    wire [7 : 0]                    read_len_total_for_data_ch;
    //reg  [AXI_DATA_WIDTH/DOCW : 0]  unalign_read_offset_total;
    wire [AXI_DATA_WIDTH-1 : 0]     rmask_first;
    wire [AXI_DATA_WIDTH-1 : 0]     rmask_last;
    reg  [AXI_DATA_WIDTH-1 : 0]     rmask;
    reg  [AXI_LIB_WIDTH-1 : 0]      read_mem_index;
    reg                             read_fifo_wr_en;
    //reg  [AXI_LIB_WIDTH-1 : 0]      read_fifo_wr_cnt_advance;
    reg  [AXI_LIB_WIDTH-1 : 0]      read_fifo_wr_cnt;
    //reg  [AXI_LIB_WIDTH-1 : 0]      read_fifo_wr_cnt_ff;
    wire [AXI_DATA_WIDTH-1 : 0]     rdata_i;
 
    reg  [AXI_LIB_WIDTH-1 : 0]      read_data_cnt;
    reg [4 : 0]                     read_data_valid_byte;
    wire                            read_data_save_done;
    reg                             read_data_save_done_ff;
    wire                            read_data_save_done_pos;
    reg  [AXI_ID_WIDTH-1 : 0]       read_rid_r;
    reg  [1 : 0]                    read_resp_r;
    reg                             read_almost_done;
    reg                             read_almost_done_ff;
    reg                             start_read_r;

    assign M_AXI_ARID_O     = instr_id;
    assign M_AXI_ARADDR_O   = instr_addr;
    assign M_AXI_ARLEN_O    = axi_arvalid ? read_len_for_addr_ch : 8'd0;
    assign M_AXI_ARSIZE_O   = $unsigned(clogb2((AXI_DATA_WIDTH/8))-1);
    assign M_AXI_ARBURST_O  = 2'b01;
    assign M_AXI_ARLOCK_O   = 1'b0;
    assign M_AXI_ARCACHE_O  = 4'b0010;
    assign M_AXI_ARPROT_O   = 3'h0;
    assign M_AXI_ARQOS_O    = 4'h0;
    assign M_AXI_ARUSER_O   = 'b1;
    assign M_AXI_ARVALID_O  = axi_arvalid;
    assign M_AXI_RREADY_O   = axi_rready;
    
    assign rdata_i                 = M_AXI_RDATA_I & rmask;
    assign read_fsm_state_o        = r_data_state;
    assign read_ready_o            = bchk_ready & ~start_read_r;
    //assign read_ready_o            = bchk_ready & ~start_read_i;
    //assign start_read_data_pulse   = ((r_data_state == R_WDATA) && (r_data_state_r == R_IDLE)) || (read_data_save_done_pos && read_transaction_cnt > 'd1);
    assign start_read_data_pulse   = (r_data_state == R_WDATA) && (r_data_state_r == R_IDLE || r_data_state_r == R_CHKID);
    assign rnext                   = M_AXI_RVALID_I & M_AXI_RREADY_O;
    assign instr_addr_end          = instr_addr + instr_bytes;
    assign write_addr_done         = M_AXI_ARREADY_I && axi_arvalid;
    assign data_fsm_addr_end_part  = data_fsm_addr + data_fsm_bytes;
    assign data_fsm_addr_end_total = data_fsm_addr + data_fsm_bytes_total;
    assign read_len_for_data_ch    = data_fsm_addr_end_part[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT] - data_fsm_addr[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT];
    assign read_len_total_for_data_ch = data_fsm_addr_end_total[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT] - data_fsm_addr[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT];
    
    assign rmask_last              = {AXI_DATA_WIDTH{1'b1}} >> (($unsigned((AXI_DATA_WIDTH/8)) -'d1 - (data_fsm_addr_end_part%ADDR_ALIGN_BOUNDARY)) * 'd8);
    assign read_data_or_desc_sel_o = (data_fsm_rdesc_en) ? 2'b10 : 2'b01;
    assign read_desc_fifo_sel_o    = (data_fsm_rdesc_type) ? 2'b10 : 2'b01;
    assign read_data_txfifo_sel_o  = (data_fsm_fifo_sel) ? 2'b10 : 2'b01;
    assign read_data_done_o        = ~data_fsm_rdesc_en && read_data_done && data_fsm_end_of_a_task;
    assign read_desc_done_o        = data_fsm_rdesc_en && read_data_done && data_fsm_end_of_a_task;
    //assign read_almost_done_o      = (r_data_state == R_WDATA) && (read_transaction_cnt <= 'd2);
    assign read_data_save_done     = (data_fsm_end_of_a_task && rdone_flag && (read_fifo_wr_cnt >= read_data_cnt)) || (~data_fsm_end_of_a_task && read_data_done);
    assign read_data_save_done_pos = ~read_data_save_done_ff && read_data_save_done;
    assign read_fifo_wr_en_o       = read_fifo_wr_en && ~read_fifo_wr_full_i;
    assign read_data_almost_done_o = ~data_fsm_rdesc_en && ~read_almost_done_ff && read_almost_done && data_fsm_end_of_a_task; 
    assign read_desc_almost_done_o = data_fsm_rdesc_en && ~read_almost_done_ff && read_almost_done && data_fsm_end_of_a_task;  
    
`ifndef AXI_UNALIGN_EN

    localparam integer MEM_MAX_DEPTH       = 'd64;
    assign rmask_first     = {AXI_DATA_WIDTH{1'b1}};
    assign bchk_addr_align = {bchk_addr[39:4], 4'b0};//bchk_addr - (bchk_addr % ADDR_ALIGN_BOUNDARY);
    
`endif

`ifdef AXI_UNALIGN_EN

    localparam integer MEM_MAX_DEPTH   = 'd64;
    localparam integer MEM_AW          = $unsigned(clogb2(MEM_MAX_DEPTH)) -'d1;
    
    wire [DOCW-1 : 0]               unalign_read_offset;
    reg  [DOCW-1 : 0]               unalign_read_offset_r;
    reg  [7 : 0]                    read_mem[0 : MEM_MAX_DEPTH-1];
    
    reg  [AXI_LIB_WIDTH-1 : 0]      initial_mem_cnt;  
    
    assign unalign_read_offset = read_active ? (data_fsm_addr%ADDR_ALIGN_BOUNDARY) : 'd0;
    assign rmask_first         = {AXI_DATA_WIDTH{1'b1}} << (unalign_read_offset * 'd8);
    assign bchk_addr_align     = bchk_addr;
    
`endif

    /*
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin
        if(~M_AXI_ARESETN_I)
            read_data_almost_done_o <= 1'b0;
        else if((read_index_total == 'd2) && (read_index_total_ff == 'd1))
            read_data_almost_done_o <= 1'b1;
        else
            read_data_almost_done_o <= 1'b0;
    end
    */
    always @(*)
    begin
        if (r_addr_state == R_WADDR)
            read_len_for_addr_ch = instr_addr_end[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT] - instr_addr[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT];
        else
            read_len_for_addr_ch = 'd0;
    end
    
    always @(*)                                   
    begin
        case(r_addr_state)
            R_FETCH_INSTR:
            begin
                if (~instr_fifo_empty && read_transaction_cnt <= $unsigned(AXI_MAX_INSTR_NUM-1))//The fifo is not empty and there are less than AXI_MAX_TRN_NUM pending transactions.
                    instr_fifo_rd_en = 1'b1; //Generate a high level to fetch an write instruction.
                else
                    instr_fifo_rd_en = 1'b0; //Otherwise, do not fetch write instruction.
            end
            default:
                instr_fifo_rd_en = 1'b0;
        endcase                          
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if (~M_AXI_ARESETN_I)
        begin
			r_data_state_r           <= 'd0;
            start_read_data_pulse_ff <= 1'b0;
			start_read_data_pulse_r  <= 'd0;
			read_data_save_done_ff   <= 'b0;
			read_index_total_ff      <= 'd0;
            start_read_r             <= 1'b0;
        end
        else
        begin
			r_data_state_r           <= r_data_state;
            start_read_data_pulse_ff <= start_read_data_pulse;
			start_read_data_pulse_r  <= start_read_data_pulse_ff;
			read_data_save_done_ff   <= read_data_save_done;
			read_index_total_ff      <= read_index_total;
            start_read_r             <= start_read_i;
        end
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
       if (~M_AXI_ARESETN_I)
       begin
           r_addr_state <= R_IDLE;
       end
       else
       begin
           r_addr_state <= r_addr_next_state;
       end
    end
    
    always @(*)
    begin
        case(r_addr_state)
            R_IDLE://01
                if(~instr_fifo_empty || instr_fifo_wr_en == 1'b1)
                    r_addr_next_state = R_FETCH_INSTR;
                else
                    r_addr_next_state = R_IDLE;
            R_FETCH_INSTR://02
                if(instr_fifo_empty == 1'b1)
                begin
                    r_addr_next_state = R_IDLE;
                end
                else
                begin
                    if(read_transaction_cnt >= AXI_MAX_INSTR_NUM)
                        r_addr_next_state = R_FETCH_INSTR;
                    else
                        r_addr_next_state = R_WADDR;
                end
            R_WADDR://04
            begin
                if(write_addr_done == 1'b1)
                begin
                    r_addr_next_state = R_FETCH_INSTR;
                end
                else
                begin
                    r_addr_next_state = R_WADDR;
                end
            end
            default:
                r_addr_next_state = R_IDLE;
        endcase
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if (~M_AXI_ARESETN_I)
        begin
            r_data_state <= R_IDLE;
        end
        else
            r_data_state <= r_data_next_state;
    end
    
    always @(*)
    begin
        case(r_data_state)
            R_IDLE:
            begin
                if (read_transaction_cnt != 'd0 | write_addr_done == 1'b1)
                    r_data_next_state = R_WDATA;
                else
                    r_data_next_state = R_IDLE;
            end
            R_WDATA:
            begin
                if(read_data_save_done_pos == 1'b1)
                    r_data_next_state = R_CHKID;
                else
                    r_data_next_state = R_WDATA;
            end
            R_CHKID:
            begin
                if((read_rid_r == data_fsm_id) && (read_resp_r == 2'b00))
                begin
                    if(read_transaction_cnt != 'd0)
                        r_data_next_state = R_WDATA;
                    else
                        r_data_next_state = R_IDLE;
                end
                else
                    r_data_next_state = R_PAUSE;
            end
            R_PAUSE:
            begin
                r_data_next_state = R_IDLE;
            end
            default:
                r_data_next_state = R_IDLE;
        endcase
    end

    always @ (posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                   
    begin                                                                     
        if (~M_AXI_ARESETN_I)                      
            axi_arvalid <= 1'b0;
        else if (M_AXI_ARREADY_I && axi_arvalid)
            axi_arvalid <= 1'b0;
        else if((r_addr_state == R_WADDR) && (read_transaction_cnt < AXI_MAX_INSTR_NUM))
        //else if(((r_addr_state == R_FETCH_INSTR) || (r_addr_state == R_WADDR)) && ~instr_fifo_empty && (read_transaction_cnt < AXI_MAX_INSTR_NUM))
        begin
            if (~axi_arvalid & ~write_addr_done)
                axi_arvalid <= 1'b1;
            else
                axi_arvalid <= axi_arvalid;
        end
        else
            axi_arvalid <= axi_arvalid;
        /*
        else case(r_addr_state)
                R_WADDR:
                    if (~axi_arvalid & ~write_addr_done)
                        axi_arvalid <= 1'b1;
                    else if (M_AXI_ARREADY_I && axi_arvalid)
                        axi_arvalid <= 1'b0;
                    else
                        axi_arvalid <= axi_arvalid;
               default:
                    axi_arvalid <= 1'b0;
        endcase
        */                           
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin                                                                                                     
        if (~M_AXI_ARESETN_I)                                                                                 
            read_active <= 1'b0;                                                                           
        else if (start_read_data_pulse_r) //start***pulse_r(with data_fsm_begin_of_a_task from fifo) is used to reset ptr&cnt. axi_rready must be asserted AFTER reset ptr&cnt.                                                                     
            read_active <= 1'b1;                                        
        else if (read_data_done)                                                         
            read_active <= 1'b0;        
        else
            read_active <= read_active;                                                     
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                          
    begin                                                                 
        if (~M_AXI_ARESETN_I)                                           
            axi_rready <= 1'b0;
        else if(M_AXI_RVALID_I && M_AXI_RLAST_I && axi_rready)
            axi_rready <= 1'b0;
        else if(read_active == 1'b1 && ~rdone_flag && ~read_fifo_wr_full_i && (read_fifo_wr_cnt + MEM_MAX_DEPTH - 'd16 > read_data_cnt))
        //else if(read_active == 1'b1 && ~rdone_flag && ~read_fifo_wr_full_i && (read_fifo_wr_cnt + MEM_MAX_DEPTH - 'd16 > read_data_cnt))
            axi_rready <= 1'b1;
        else
            axi_rready <= 1'b0;
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                               
    begin                                                                 
        if (~M_AXI_ARESETN_I)
            read_index <= 'b0; 
        else if(start_read_data_pulse)                                    
            read_index <= 'b0;                                           
        else if(rnext && (read_index != {1'b0, read_len_for_data_ch}))
            read_index <= read_index + 'b1;                              
        else                                                                
            read_index <= read_index;                                         
    end 
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                               
    begin                                                                 
        if (~M_AXI_ARESETN_I)
            read_index_total <= 'b0; 
        else if(start_read_data_pulse_r && data_fsm_begin_of_a_task)                                                        
            read_index_total <= 'b0;                                     
        else if(rnext && (read_index_total != read_len_total_for_data_ch + 'd1))                               
            read_index_total <= read_index_total + 'b1;                        
        else                                                                
            read_index_total <= read_index_total;                                         
    end 
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin                                                                                                     
        if (~M_AXI_ARESETN_I)
        begin
            read_data_done <= 1'b0;
            rdone_flag     <= 1'b0;
        end
        else if(start_read_data_pulse)
        begin                                                                              
            read_data_done <= 1'b0;
            rdone_flag     <= 1'b0;
        end
        else if(~rdone_flag && ~read_data_done && read_active && rnext && M_AXI_RLAST_I && (read_index == {1'b0, read_len_for_data_ch}))
        begin                  
            read_data_done <= 1'b1;
            rdone_flag     <= 1'b1;
        end                                                                                 
        else 
        begin                                                                         
            read_data_done <= 1'b0; 
            rdone_flag     <= rdone_flag;
        end                                                                        
    end
    
    //read_rid_r
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin                                                                                                     
        if (~M_AXI_ARESETN_I)
        begin
            read_rid_r   <= 'd0;
            read_resp_r  <= 'd0;
        end
        else if(rnext)
        begin
            read_rid_r  <= M_AXI_RID_I;
            read_resp_r <= M_AXI_RRESP_I;
        end
        else
        begin
            read_resp_r <= read_resp_r;
            
        end
    end
    
    //assign read_fifo_wr_cnt = (read_fifo_wr_full_i) ? read_fifo_wr_cnt_ff : read_fifo_wr_cnt_advance;
    //always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    always @(*)
    begin
        if(rdone_flag && data_fsm_end_of_a_task && (read_fifo_wr_cnt < read_data_cnt))
            read_fifo_wr_en = 1'b1;
        else if(read_active && (read_fifo_wr_cnt +'d32 <= read_data_cnt))
            read_fifo_wr_en = 1'b1;
        else
            read_fifo_wr_en = 1'b0;
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if(~M_AXI_ARESETN_I)
            read_fifo_wr_cnt <= 'd0;
        else if(start_read_data_pulse_r && data_fsm_begin_of_a_task)
            read_fifo_wr_cnt <= 'd0;
        else if(read_fifo_wr_en_o)
            read_fifo_wr_cnt  <= read_fifo_wr_cnt + FIFO_LINE_LEN_BYTE;
        else
            read_fifo_wr_cnt  <= read_fifo_wr_cnt;
    end
    
    
    always @ (*)
    begin
        case({ rmask[120], rmask[112], rmask[104], rmask[96],
               rmask[88],  rmask[80],  rmask[72],  rmask[64],
               rmask[56],  rmask[48],  rmask[40],  rmask[32],
               rmask[24],  rmask[16],  rmask[8],   rmask[0]    })
            'b0000_0000_0000_0000: read_data_valid_byte = 'd0;
            'b0000_0000_0000_0001: read_data_valid_byte = 'd1;
            'b0000_0000_0000_0011: read_data_valid_byte = 'd2;
            'b0000_0000_0000_0111: read_data_valid_byte = 'd3;
            'b0000_0000_0000_1111: read_data_valid_byte = 'd4;
            'b0000_0000_0001_1111: read_data_valid_byte = 'd5;
            'b0000_0000_0011_1111: read_data_valid_byte = 'd6;
            'b0000_0000_0111_1111: read_data_valid_byte = 'd7;
            'b0000_0000_1111_1111: read_data_valid_byte = 'd8;
            'b0000_0001_1111_1111: read_data_valid_byte = 'd9;
            'b0000_0011_1111_1111: read_data_valid_byte = 'd10;
            'b0000_0111_1111_1111: read_data_valid_byte = 'd11;
            'b0000_1111_1111_1111: read_data_valid_byte = 'd12;
            'b0001_1111_1111_1111: read_data_valid_byte = 'd13;
            'b0011_1111_1111_1111: read_data_valid_byte = 'd14;
            'b0111_1111_1111_1111: read_data_valid_byte = 'd15;
            'b1111_1111_1111_1111: read_data_valid_byte = 'd16;
`ifdef AXI_UNALIGN_EN
            'b1111_1111_1111_1110: read_data_valid_byte = 'd15;
            'b1111_1111_1111_1100: read_data_valid_byte = 'd14;
            'b1111_1111_1111_1000: read_data_valid_byte = 'd13;
            'b1111_1111_1111_0000: read_data_valid_byte = 'd12;
            'b1111_1111_1110_0000: read_data_valid_byte = 'd11;
            'b1111_1111_1100_0000: read_data_valid_byte = 'd10;
            'b1111_1111_1000_0000: read_data_valid_byte = 'd9;
            'b1111_1111_0000_0000: read_data_valid_byte = 'd8;
            'b1111_1110_0000_0000: read_data_valid_byte = 'd7;
            'b1111_1100_0000_0000: read_data_valid_byte = 'd6;
            'b1111_1000_0000_0000: read_data_valid_byte = 'd5;
            'b1111_0000_0000_0000: read_data_valid_byte = 'd4;
            'b1110_0000_0000_0000: read_data_valid_byte = 'd3;
            'b1100_0000_0000_0000: read_data_valid_byte = 'd2;
            'b1000_0000_0000_0000: read_data_valid_byte = 'd1;
`endif     
            default: read_data_valid_byte = 'd0;
        endcase
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if(~M_AXI_ARESETN_I)
            read_data_cnt <= 'd0;
        else if(start_read_data_pulse_r && data_fsm_begin_of_a_task)
            read_data_cnt <= 'd0;
        else if(rnext)
            read_data_cnt <= read_data_cnt + read_data_valid_byte;
        else
            read_data_cnt <= read_data_cnt;
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if(~M_AXI_ARESETN_I)
            read_mem_index <= 'd0;
        else if(start_read_data_pulse_r && data_fsm_begin_of_a_task)
            //read_mem_index <= AXI_UNALIGN_ADDR_EN ? unalign_read_offset : 'd0;
            read_mem_index <= 'd0;
        else if(read_fifo_wr_en_o)
            read_mem_index <= read_mem_index + 'd1;
        else
            read_mem_index <= read_mem_index;
    end
    
    /*
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if(~M_AXI_ARESETN_I || (start_read_data_pulse_r && data_fsm_begin_of_a_task))
            read_fifo_wr_cnt <= 'd0;
        else if(read_active && (read_fifo_wr_cnt < read_data_cnt))
            read_fifo_wr_cnt <= read_fifo_wr_cnt + FIFO_LINE_LEN_BYTE;
        else
            read_fifo_wr_cnt <= read_fifo_wr_cnt;
    end
    */
    
        
`ifndef AXI_UNALIGN_EN
    reg  [AXI_DATA_WIDTH-1 : 0] read_cache_0;     
    reg  [AXI_DATA_WIDTH-1 : 0] read_cache_1;     
    reg  [AXI_DATA_WIDTH-1 : 0] read_cache_2;     
    reg  [AXI_DATA_WIDTH-1 : 0] read_cache_3;     
                                                   
    wire [1 : 0]                read_cahce_wr_ptr;
    
    assign read_cahce_wr_ptr = read_index_total % 'd4;
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if(~M_AXI_ARESETN_I)
        begin
            read_cache_0   <= 'd0;
            read_cache_1   <= 'd0;
            read_cache_2   <= 'd0;
            read_cache_3   <= 'd0;
        end
        else 
        begin
            case(read_cahce_wr_ptr)
                2'd0:
                begin
                    read_cache_0   <= rdata_i;
                    read_cache_1   <= read_cache_1;
                    read_cache_2   <= read_cache_2;
                    read_cache_3   <= read_cache_3;
                end
                2'd1:
                begin
                    read_cache_0   <= read_cache_0;
                    read_cache_1   <= rdata_i;
                    read_cache_2   <= read_cache_2;
                    read_cache_3   <= read_cache_3;
                end
                2'd2:
                begin
                    read_cache_0   <= read_cache_0;
                    read_cache_1   <= read_cache_1;
                    read_cache_2   <= rdata_i;
                    read_cache_3   <= read_cache_3;
                end
                2'd3:
                begin
                    read_cache_0   <= read_cache_0;
                    read_cache_1   <= read_cache_1;
                    read_cache_2   <= read_cache_2; 
                    read_cache_3   <= rdata_i;
                end
                default:
                begin
                    read_cache_0   <= read_cache_0;
                    read_cache_1   <= read_cache_1;
                    read_cache_2   <= read_cache_2;
                    read_cache_3   <= read_cache_3;
                end
            endcase
        end
    end
    
    wire [1 : 0] read_cache_rd_prt;
    
    assign read_cache_rd_prt = read_mem_index % 'd4;
    
    always @(*)
    begin
        case(read_cache_rd_prt)
            2'd0: read_data_o = read_cache_0;
            2'd1: read_data_o = read_cache_1;
            2'd2: read_data_o = read_cache_2;
            2'd3: read_data_o = read_cache_3;
            default: read_data_o = 'd0;
        endcase
    end
    
`endif

`ifdef AXI_UNALIGN_EN
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin
        if(~M_AXI_ARESETN_I)
            unalign_read_offset_r <= 'd0;
        else if(start_read_data_pulse_r && data_fsm_begin_of_a_task)
            unalign_read_offset_r <= unalign_read_offset;
        else
            unalign_read_offset_r <= unalign_read_offset_r;
    end
    
    wire [MEM_AW-1 : 0] read_index_total_0 ;
    wire [MEM_AW-1 : 0] read_index_total_1 ;
    wire [MEM_AW-1 : 0] read_index_total_2 ;
    wire [MEM_AW-1 : 0] read_index_total_3 ;
    wire [MEM_AW-1 : 0] read_index_total_4 ;
    wire [MEM_AW-1 : 0] read_index_total_5 ;
    wire [MEM_AW-1 : 0] read_index_total_6 ;
    wire [MEM_AW-1 : 0] read_index_total_7 ;
    wire [MEM_AW-1 : 0] read_index_total_8 ;
    wire [MEM_AW-1 : 0] read_index_total_9 ;
    wire [MEM_AW-1 : 0] read_index_total_10 ;
    wire [MEM_AW-1 : 0] read_index_total_11 ;
    wire [MEM_AW-1 : 0] read_index_total_12 ;
    wire [MEM_AW-1 : 0] read_index_total_13 ;
    wire [MEM_AW-1 : 0] read_index_total_14 ;
    wire [MEM_AW-1 : 0] read_index_total_15 ;
    
    assign read_index_total_0  = read_index_total * 'd16 + 'd0 ;
    assign read_index_total_1  = read_index_total * 'd16 + 'd1 ;
    assign read_index_total_2  = read_index_total * 'd16 + 'd2 ;
    assign read_index_total_3  = read_index_total * 'd16 + 'd3 ;
    assign read_index_total_4  = read_index_total * 'd16 + 'd4 ;
    assign read_index_total_5  = read_index_total * 'd16 + 'd5 ;
    assign read_index_total_6  = read_index_total * 'd16 + 'd6 ;
    assign read_index_total_7  = read_index_total * 'd16 + 'd7 ;
    assign read_index_total_8  = read_index_total * 'd16 + 'd8 ;
    assign read_index_total_9  = read_index_total * 'd16 + 'd9 ;
    assign read_index_total_10 = read_index_total * 'd16 + 'd10;
    assign read_index_total_11 = read_index_total * 'd16 + 'd11;
    assign read_index_total_12 = read_index_total * 'd16 + 'd12;
    assign read_index_total_13 = read_index_total * 'd16 + 'd13;
    assign read_index_total_14 = read_index_total * 'd16 + 'd14;
    assign read_index_total_15 = read_index_total * 'd16 + 'd15;

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if(~M_AXI_ARESETN_I)
        begin
            for(initial_mem_cnt = 'd0; initial_mem_cnt < MEM_MAX_DEPTH; initial_mem_cnt = initial_mem_cnt + 'd1)
               read_mem[initial_mem_cnt] <= 'd0;
        end
//        else if(start_read_data_pulse_r && data_fsm_begin_of_a_task)
//        begin
//            for(initial_mem_cnt = 'd0; initial_mem_cnt < MEM_MAX_DEPTH; initial_mem_cnt = initial_mem_cnt + 'd1)
//               read_mem[initial_mem_cnt] <= 'd0;
//        end
        else if(rnext)
        //if(rnext)
        begin
            {
                read_mem[read_index_total_15],
                read_mem[read_index_total_14],
                read_mem[read_index_total_13],
                read_mem[read_index_total_12],
                read_mem[read_index_total_11],
                read_mem[read_index_total_10],
                read_mem[read_index_total_9 ],
                read_mem[read_index_total_8 ],
                read_mem[read_index_total_7 ],
                read_mem[read_index_total_6 ],
                read_mem[read_index_total_5 ],
                read_mem[read_index_total_4 ],
                read_mem[read_index_total_3 ],
                read_mem[read_index_total_2 ],
                read_mem[read_index_total_1 ],
                read_mem[read_index_total_0 ]
            } <= rdata_i;
        end
    end
    
    wire  [MEM_AW-1 : 0]  read_mem_ptr_15;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_14;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_13;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_12;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_11;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_10;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_9 ;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_8 ;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_7 ;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_6 ;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_5 ;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_4 ;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_3 ;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_2 ;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_1 ;
    wire  [MEM_AW-1 : 0]  read_mem_ptr_0 ;
    
    assign read_mem_ptr_15  = read_mem_index * 'd16 + unalign_read_offset_r + 'd15;
    assign read_mem_ptr_14  = read_mem_index * 'd16 + unalign_read_offset_r + 'd14;
    assign read_mem_ptr_13  = read_mem_index * 'd16 + unalign_read_offset_r + 'd13;
    assign read_mem_ptr_12  = read_mem_index * 'd16 + unalign_read_offset_r + 'd12;
    assign read_mem_ptr_11  = read_mem_index * 'd16 + unalign_read_offset_r + 'd11;
    assign read_mem_ptr_10  = read_mem_index * 'd16 + unalign_read_offset_r + 'd10;
    assign read_mem_ptr_9   = read_mem_index * 'd16 + unalign_read_offset_r + 'd9 ;
    assign read_mem_ptr_8   = read_mem_index * 'd16 + unalign_read_offset_r + 'd8 ;
    assign read_mem_ptr_7   = read_mem_index * 'd16 + unalign_read_offset_r + 'd7 ;
    assign read_mem_ptr_6   = read_mem_index * 'd16 + unalign_read_offset_r + 'd6 ;
    assign read_mem_ptr_5   = read_mem_index * 'd16 + unalign_read_offset_r + 'd5 ;
    assign read_mem_ptr_4   = read_mem_index * 'd16 + unalign_read_offset_r + 'd4 ;
    assign read_mem_ptr_3   = read_mem_index * 'd16 + unalign_read_offset_r + 'd3 ;
    assign read_mem_ptr_2   = read_mem_index * 'd16 + unalign_read_offset_r + 'd2 ;
    assign read_mem_ptr_1   = read_mem_index * 'd16 + unalign_read_offset_r + 'd1 ;
    assign read_mem_ptr_0   = read_mem_index * 'd16 + unalign_read_offset_r + 'd0 ;
    
    //always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    always @(*)
    begin
//        if(~M_AXI_ARESETN_I)
//            read_data_o <= 'd0;
//        else
            read_data_o = {
                            read_mem[read_mem_ptr_15],
                            read_mem[read_mem_ptr_14],
                            read_mem[read_mem_ptr_13],
                            read_mem[read_mem_ptr_12],
                            read_mem[read_mem_ptr_11],
                            read_mem[read_mem_ptr_10],
                            read_mem[read_mem_ptr_9 ],
                            read_mem[read_mem_ptr_8 ],
                            read_mem[read_mem_ptr_7 ],
                            read_mem[read_mem_ptr_6 ],
                            read_mem[read_mem_ptr_5 ],
                            read_mem[read_mem_ptr_4 ],
                            read_mem[read_mem_ptr_3 ],
                            read_mem[read_mem_ptr_2 ],
                            read_mem[read_mem_ptr_1 ],
                            read_mem[read_mem_ptr_0 ]
                          };
    end
    
`endif
    
    always @(*)
    begin
        if(read_index == 'd0 && read_len_for_data_ch == 'd0)
            rmask = rmask_first & rmask_last;
        else if(read_index == 'd0 && read_len_for_data_ch != 'd0)
            rmask = rmask_first;
        else if(read_index != 'd0 && read_index != {1'b0, read_len_for_data_ch})
            rmask = {AXI_DATA_WIDTH{1'b1}};
        else if(read_index != 'd0 && read_index == {1'b0, read_len_for_data_ch})
            rmask = rmask_last;
        else
            rmask = 'd0;
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin
        if(~M_AXI_ARESETN_I)
            read_almost_done <= 1'b0;
        else if(start_read_data_pulse_r && data_fsm_begin_of_a_task)
            read_almost_done <= 1'b0;
        else if(read_data_done)
            read_almost_done <= 1'b0;
        else if(read_active && (read_len_total_for_data_ch <= 'd4 + read_index_total))
            read_almost_done <= 1'b1;
        else
            read_almost_done <= read_almost_done;
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin
        if(~M_AXI_ARESETN_I)
            read_almost_done_ff <= 1'b0;
        else
            read_almost_done_ff <= read_almost_done;
    end
    
    axi_boundary_checker #
    (
        .CROSS_4K_EN         (1'b1),
        .AXI_ID_WIDTH        (AXI_ID_WIDTH),
        .AXI_ADDR_WIDTH      (AXI_ADDR_WIDTH),
        .AXI_DATA_WIDTH      (AXI_DATA_WIDTH),
        .AXI_LIB_WIDTH       (AXI_LIB_WIDTH)
    )
    read_boundary_checker
    (
        .clk             (M_AXI_ACLK_I),
        .rstn            (M_AXI_ARESETN_I),
        .id_i            (read_id_i),
        .addr_i          (read_addr_i),
        .wbytes_i        (read_len_in_byte_i),
        .desc_en_i       (read_desc_en_i),
        .desc_type_i     (read_desc_type_i),
        .fifo_sel_i      (read_data_10g40g_sel_i),
        .din_ready       (bchk_ready),
        .din_valid       (start_read_i),
        .id_o            (bchk_id),
        .addr_o          (bchk_addr),
        .wbytes_o        (bchk_bytes),
        .wbytes_total_o  (bchk_bytes_total),
        .desc_en_o       (bchk_rdesc_en),
        .desc_type_o     (bchk_rdesc_type),
        .fifo_sel_o      (bchk_fifo_sel),
        .begin_of_a_task (bchk_begin_of_a_task),
        .end_of_a_task   (bchk_end_of_a_task),
        .dout_ready      (~instr_fifo_full),
        .dout_valid      (instr_fifo_wr_en)
    );
`ifdef VCS_MODEL 
    axi_sfifo_2T #(.DW (INSTR_WIDTH),  .AW (TRN_CNT_WITDH-1)) 
    read_addr_channel_instr_fifo
    (
        .clk         (M_AXI_ACLK_I),
        .rstn        (M_AXI_ARESETN_I),
        .wr_en       (instr_fifo_wr_en),
        .rd_en       (instr_fifo_rd_en),
        .din         ({bchk_id, bchk_addr_align, bchk_bytes}),
        .dout        ({instr_id, instr_addr, instr_bytes}),
        .empty       (instr_fifo_empty),
        .full        (),              
        .almost_full (instr_fifo_full)
    );
    axi_sfifo_2T #(.DW (DATAFSM_FIFO_DW),  .AW (TRN_CNT_WITDH)) 
    read_data_channel_instr_fifo
    (
        .clk         (M_AXI_ACLK_I),
        .rstn        (M_AXI_ARESETN_I),
        .wr_en       (instr_fifo_wr_en),
        .rd_en       (start_read_data_pulse),
        .din         ({bchk_end_of_a_task, bchk_begin_of_a_task, bchk_fifo_sel, bchk_rdesc_type, bchk_rdesc_en, bchk_addr_align, bchk_bytes_total, bchk_bytes, bchk_id}),
        .dout        ({data_fsm_end_of_a_task, data_fsm_begin_of_a_task, data_fsm_fifo_sel, data_fsm_rdesc_type, data_fsm_rdesc_en, data_fsm_addr, data_fsm_bytes_total, data_fsm_bytes, data_fsm_id}),
        .empty       (),
        .full        (),
        .almost_full ()
    );
`else
    sync_fifo_d32_w57 read_addr_channel_instr_fifo
    (
        .clk         (M_AXI_ACLK_I),
        .rstn        (M_AXI_ARESETN_I),
        .wr_en       (instr_fifo_wr_en),
        .rd_en       (instr_fifo_rd_en),
        .din         ({bchk_id, bchk_addr_align, bchk_bytes}),
        .dout        ({instr_id, instr_addr, instr_bytes}),
        .empty       (instr_fifo_empty),
        .full        (),              
        .almost_full (instr_fifo_full),
        .rf_2p_cfg_register    (rf_2p_cfg_register)
    );

    sync_fifo_d64_w73 read_data_channel_instr_fifo
    (
        .clk         (M_AXI_ACLK_I),
        .rstn        (M_AXI_ARESETN_I),
        .wr_en       (instr_fifo_wr_en),
        .rd_en       (start_read_data_pulse),
        .din         ({bchk_end_of_a_task, bchk_begin_of_a_task, bchk_fifo_sel, bchk_rdesc_type, bchk_rdesc_en, bchk_addr_align, bchk_bytes_total, bchk_bytes, bchk_id}),
        .dout        ({data_fsm_end_of_a_task, data_fsm_begin_of_a_task, data_fsm_fifo_sel, data_fsm_rdesc_type, data_fsm_rdesc_en, data_fsm_addr, data_fsm_bytes_total, data_fsm_bytes, data_fsm_id}),
        .empty       (),
        .full        (),
        .almost_full (),
        .ram_2p_cfg_register    (ram_2p_cfg_register)
    );
`endif

    axi_instr_counter #(.COUNTER_WIDTH (TRN_CNT_WITDH)) read_transaction_counter
    (
        .clk        (M_AXI_ACLK_I),
        .rstn       (M_AXI_ARESETN_I),
        .add        (write_addr_done),
        .sub        (read_data_save_done_pos),
        .cnt        (read_transaction_cnt)
    );

endmodule
